package com.kk.algorithm.kmp;

import java.util.Arrays;
/*
 * @Description:    KMP 字符串匹配算法
 * @Author:         阿K
 * @CreateDate:     2021/2/6 23:11
 * @Param:
 * @Return:
**/
public class KMPAlgorithm {
    public static void main(String[] args) {
        String str1 = "BBC ABCDAB ABCDABCDABDE";
        String str2 = "ABCDABD";
        int[] next = kmpNext (str2);
        System.out.println ("匹配表：" + Arrays.toString (next));
        int result = kmpSearch (str1, str2, next);
        System.out.println ("index:" + result);
    }

    /**
     * kmp 搜索算法
     *
     * @param s1   原始字符串
     * @param s2   匹配子串
     * @param next 部分匹配表（对应匹配子串 s2）
     * @return     返回第一个拼配位置，失配返回 -1
     */
    public static int kmpSearch(String s1, String s2, int[] next) {
        for (int i = 0, j = 0; i < s1.length ( ); i++) {
            // 情况分两种，一是不匹配到，要回溯；二是匹配到 索引 +1

            // 难点：情况一：当dest.charAt(i) !=dest.charAt(j)，则需要 next[j-1] 得到新的 j
            // 公式，不必纠结其诞生原因
            if (j > 0 && s1.charAt (i) != s2.charAt (j)) {
                j = next[j - 1];
            }

            // 情况二：匹配到，匹配值数量 +1
            if (s1.charAt (i) == s2.charAt (j)) {
                j++;
            }

            // 找到了
            if (j == s2.length ( )) {
                return i - j + 1;// +1 因为索引从 0 开始，i 为 主串总长
            }
        }
        return -1;
    }

    // 难点：获取一个字符串(匹配子串)的部分匹配表
    public static int[] kmpNext(String dest) {
        // 创建一个next 数组用于存储 部分匹配值
        int[] next = new int[dest.length ( )];
        // 若字符串长度为 1，部分匹配值则为 0（第一个默认就是空集 0）
        next[0] = 0;

        for (int i = 1, j = 0; i < dest.length ( ); i++) {// j 是匹配表的索引并操作字符串，i 是字符串的索引，从1开始因为，默认 一个字符是空集
            // 此时分两种情况，一是不匹配到，要回溯 ，索引 -1 ；二是匹配到 索引+1

            // 核心：情况一：当dest.charAt(i) !=dest.charAt(j)，则需要 next[j-1] 得到新的 j
            // 直到满足 dest.charAt(i) ==dest.charAt(j) 才退出循环
            while (j > 0 && dest.charAt (i) != dest.charAt (j)) {
                j = next[j - 1];// 回溯（回退 1）
            }
            // 情况二：当 满足 dest.charAt(i) ==dest.charAt(j) 时，部分匹配值数量 +1
            if (dest.charAt (i) == dest.charAt (j)) {
                j++;
            }
            // 部分匹配值赋值
            next[i] = j;
        }
        return next;
    }
}
